/*
 * Wizer interface for Wasm module to be initialized.
 *
 * This header provides several macros that allow a Wasm module written in C/C++
 * to declare an initializer function, and ensure that global constructors (in
 * C++'s case) are run at initialization time rather than on startup of the
 * pre-initialized module.
 */
#ifndef _WIZER_H_
#define _WIZER_H_

#ifdef __cplusplus
#define __WIZER_EXTERN_C extern "C"
#else
#define __WIZER_EXTERN_C extern
#endif

#ifdef __clang_major__
// wasi-sdk-16 was the first wasi-sdk version that shipped with a version of
// wasi-libc that did not include __original_main. However, wasi-sdk-15 shipped
// with clang-14.0.0. To correctly identify the boundary where __original_main
// no longer exists, we check for either clang-15+ or specifically clang-14.0.4.
//
// wasi-sdk-17 ships with clang-15.0.6
// wasi-sdk-16 ships with clang-14.0.4
#if __clang_major__ >= 15 || (__clang_major__ == 14 && __clang_patchlevel__ == 4)
#define WIZER_MAIN_VOID __main_void
#else
#define WIZER_MAIN_VOID __original_main
#endif
#endif

// We default to assuming that the compiler is new enough to provide
// __main_void.
#ifndef WIZER_MAIN_VOID
#define WIZER_MAIN_VOID __main_void
#endif

/*
 * This macro inserts the exported functions necessary to allow Wizer to
 * pre-initialize a Wasm module.
 *
 * To use, simply invoke the macro in exactly one compilation unit (C/C++ file)
 * that is compiled into the Wasm module to be pre-initialized:
 *
 *     static void my_init_function() { ... }
 *
 *     WIZER_INIT(my_init_function);
 *
 * (The macro refers to the provided init function, so it must have been defined
 * or must have a forward declaration at the point the macro is used.)
 *
 * The resulting module should be processed by Wizer as follows:
 *
 *     $ wizer -r _start=wizer.resume -o out.wasm in.wasm
 *
 * The result of this will be the following behavior:
 *
 * - If the `in.wasm` (the direct compilation output of a program including this
 *   macro invocation) is run directly according to the WASI ABI (i.e., by
 *   invoking `_start`), then nothing changes: global constructors are run,
 *   `main()` is invoked, then global destructors are run. The initialization
 *   function is *not* run in this case.
 *
 * - During pre-initialization (i.e., during this `wizer` invocation), global
 *   constructors will run, and then the provided initialization function will
 *   run. The module's memory and global-variable state is then snapshotted and
 *   saved into `out.wasm`.
 *
 *   All other Wizer restrictions apply (see Wizer documentation for details):
 *   for example, WASI hostcalls may be blocked, depending on options, and
 *   invoking any other imported function will result in an immediate trap
 *   and failure of the Wizer run.
 *
 * - If the resulting `out.wasm` is then run using the WASI ABI, the program's
 *   global constructors are *not* re-run. Instead, execution starts directly at
 *   `main()`, using the heap and global-variable state left by the global
 *   constructor and initialization function execution during the Wizer
 *   invocation.
 *
 * If no initialization function is needed (i.e., only C++ global constructors
 * should be run), use `WIZER_DEFAULT_INIT()` instead.
 */
#define WIZER_INIT(init_func)                                                  \
    __WIZER_EXTERN_C void __wasm_call_ctors();                                 \
    __WIZER_EXTERN_C void __wasm_call_dtors();                                 \
    __WIZER_EXTERN_C void __wasi_proc_exit(int);                               \
    __WIZER_EXTERN_C int WIZER_MAIN_VOID();                                    \
    /* This function's export name `wizer.initialize` is specially          */ \
    /* recognized by Wizer. It is the direct entry point for pre-init.      */ \
    __attribute__((export_name("wizer.initialize"))) void                      \
    __wizer_initialize() {                                                     \
        /* `__wasm_call_ctors()` is generated by `wasm-ld` and invokes all  */ \
        /* of the global constructors. It is safe (and in fact necessary)   */ \
        /* to manually invoke it here because `wizer.initialize` is the     */ \
        /* direct entry point, and no libc startup (crt1.o or equivalent)   */ \
        /* is executed before this code does. */                               \
        __wasm_call_ctors();                                                   \
        /* We now invoke the provided init function before returning.       */ \
        init_func();                                                           \
    }                                                                          \
    /* This function replaces `_start` (the WASI-specified entry point) in  */ \
    /* the pre-initialized Wasm module.                                     */ \
    __attribute__((export_name("wizer.resume"))) void __wizer_resume() {       \
        /* `__main_void()` is defined by the WASI SDK toolchain due to      */ \
        /* special semantics in C/C++ for the `main()` function, i.e., ito  */ \
        /* can either take argc/argv or not. It collects arguments using    */ \
        /* the appropriate WASI calls and then invokes the user program's   */ \
        /* `main()`. This may change in the future; when it does, we will   */ \
        /* coordinate with the WASI-SDK toolchain to implement this entry   */ \
        /* point in an alternate way. */                                       \
        int r = WIZER_MAIN_VOID();                                             \
        /* Because we are replacing `_start()`, we need to manually invoke  */ \
        /* destructors as well.                                             */ \
        __wasm_call_dtors();                                                   \
        /* If main returned non-zero code, call `__wasi_proc_exit`. */         \
        if (r != 0) {                                                          \
          __wasi_proc_exit(r);                                                 \
        }                                                                      \
    }

/*
 * This macro is like `WIZER_INIT()`, but takes no initialization function.
 * Instead, the pre-initialization phase only executes C++ global constructors
 * before snapshotting the module state.
 *
 * See documentation for `WIZER_INIT()` for more details and usage instructions.
 */
#define WIZER_DEFAULT_INIT()      \
    static void __empty_init() {} \
    WIZER_INIT(__empty_init)

#endif  // _WIZER_H_
